io.write("Loading GEO SDK... ")
--[[ GEO SDK CORE ]]--
--[[
	author:		genesis.erode / The Anger
	version:	5
	
	
	
---	NOTES ---
	
	
	[===[ classes ]===]
		
		
	<>	virtual types
		
		functions defined as
			
			***	virtual <type>	name( args )
			
		can also be written as
			
			+++	property <function>		name = function( args ) ... end
			
		the 'virtual' version is used in order to:
		
		1 - avoid confusion with what the default function will do.
		2 - avoid confusion with the expected parameters and return types.
		
		
---	EXPORTS ---
	
	
***	table	G.core.mods
	
	to register a GEO SDK module:
	
	G.core.mods[ 'newmod' ] = {
		_ = <metatable>,			-- (required) metatable for G[ 'newmod' ] object
		Ent = <metatable>,			-- metatable for Ent[ 'newmod' ] objects
		...
		<classname> = <metatable>	-- registers class 'newmod/classname', cannot be a reserved class name.
	}
	
	objects spawned with G.core.Obj( 'newmod', 'classname' ) afterwards.
	such objects have a property ['gstype'] which stores 'newmod/classname'.
	Entities are a special case, their ['gstype'] is always 'Ent'
	
	
	
***	table	G.core.tblobj( obj )
	
	returns obj if it's a table or {} otherwise
	
	
	
***	table	G.core.with( obj, assignment_obj )
	
	for every k=v pair in assignment_obj, the value is copied into obj.
	for all (k = v) in (assignment_obj) do: obj[k] = v
	
	returns obj
	
	
	
***	bool	G.core.is_gstype( obj, string class_name )
	
	returns true if obj is a GEO object and is of type class_name
	
	
	
***	<class_name>	G.core.newObj( string class_name )
or	<class_name>	G.core.newObj( string module. string class )
	
	creates and returns an object of type class_name or GetClass(object,module).
	
	
	
***	<class_name>	G.core.newObjEx( table base, string class_name )
or	<class_name>	G.core.newObjEx( table base, string module. string class )
	
	creates and returns an object of type class_name or GetClass(object,module).
	the new object will be based on table base
	
	
***	metatable	G.core.Obj_indexhelper ( metatable meta, table statics, table defaults )
	
	used to generate property-based classes' __index metatable property
	
	meta
	= existing metatable data being worked with
	
	statics
	= read-only static properties (typically methods)
	= { prop1 = value1, prop2 = value2, ... }
	
	defaults
	= properties to set during access to, and return their default values.
	= { prop1 = default1, prop2 = default2, ... }
	
	note 1 - static > default definitions for performance reasons (default would only be used once).
	note 2 - statics and defaults are only accessed once using short circuiting and rawget/rawset nesting.
	
	
	
***	seq		G.core.get_seq( child )
	
	assert( child._parent[ G.core.get_seq(child) ] == child )
	
	
	
***	parent_obj =	G.core.changeseq( parent_obj, old_seq, new_seq )
	
	cuts child from position old_seq, inserts into position new_seq.
	
	children with seq >  old_seq -- their .seq decreases by 1
	children with seq >= new_seq -- their .seq increases by 1
	
	
	
***	target_obj =	G.core.moveto( target_obj, new_parent )
or	target_obj =	G.core.moveto( target_obj )
	
	moveto first removes a parent-child relationship between target_obj and its parent, if one exists.
	next, if new_parent is supplied, it establishes a parent-child relationship.
	
	returns target_obj
	
	
	
***	void	G.core.forSubObjects( root_obj, ifunc )
	
	calls ifunc(Ent) for each sub ent recursively.
	if ifunc(Ent) returns false, children of Ent are skipped over.
	
	
	
***	void	G.core.eradicate( object )
	
	recursively cleans an object via eradicate calls, to prepare for garbage collection.
	
	warning - take care NOT to eradicate an object which is an ancestor of the current scope (such as the wheel of a car removing the car, makes no sense).
	warning 2 - doubly applies to eradicating GEO SDK core objects - yes you can, no you shouldn't.
	
	
	
***	<Ent>	G.core.newEnt( table base_object )
or	<Ent>	G.core.newEnt( void )
	
	creates an entity object. optionally a base_object may be supplied.
	
	
	
***	<Ent>	G:newEnt( table base_object )
or	<Ent>	G:newEnt( void )
or	<Ent>	G.newEnt( parent_obj, table base_object )
or	<Ent>	G.newEnt( parent_obj )
or	<Ent>	G.newEnt( void )
	
	creates an entity object and parents it to G, making it a root entity.
	only root entities (and their descendants) are considered 'in play'.
	an unparented entity can exist but it is never considered part of the game while it is unparented.
	
	the last usage is equivalent to calling G.core.Ent()
	
	
	
***	void	G.forEachEnt( ifunc )
	
	internally calls G.core.forSubObjects( G, ... )
	
	essentially a helper function to iterate over all entities 'in play'.
	
	
	
***	void		G.LoadHooks( void )
	
	binds engine callbacks to GEO SDK.
	requires state and render modules, which are included automatically at the end of this file.
	
	
	
---	CLASSES ---
	
===	ENT ===
		
	***	seq		<Ent>:seq( void )
		
		returns sibling position relative to its parent.
		refer to G.core.get_seq
		
		
	***	void	<Ent>:eradicate( void )
		
		cleans Ent for GC
		
		
	***	<Ent> 	<Ent>:changeseq( old_seq, new_seq )
		
		explicitly move a child from old_seq to new_seq.
		refer to G.core.changeseq.
		
		
	***	<Ent> 	<Ent>:moveto( parent )
		
		moves the Ent around, returns the object that was moved. refer to G.core.moveto
		
		
	***	void	<Ent>:forSubEnts( ifunc( Ent ) )
		
		runs ifunc for all sub ents recursively, does not recurse for ents returning false for ifunc.
		
		
]]--
do	_G['G'] = {core={
		tblobj = gs._tblobj;
		--[[function(o)
			return type(o)=='table' and o or {}
		end;]]--
		with = gs._with;
		--[[function(o,a)
			local t,x = G.core.tblobj(o),G.core.tblobj(a)
			local k = next(x)
			while k do
				t[k] = x[k]
				k = next(x,k)
			end
			return t
		end;]]--
		Obj_indexhelper = gs._Obj_indexhelper;
		--[[function( meta, statics, defaults )
			
			if type(defaults) ~= 'table' then
				
				if type(statics) ~= 'table' then
					-- no point in doing anything if this is the case
					return meta
				end
				
				-- no defaults, only statics, therefore: __index = statics
				return rawset( G.core.tblobj( meta ), '__index', statics )
				
			elseif type(statics) ~= 'table' then
				-- no statics, only defaults: tail-call to rawget
				return rawset( G.core.tblobj( meta ), '__index', function( object, index )
					local val = defaults[index]
					rawset( object, index, val )
					return val
				end)
			end
			
			-- both statics and defaults, statics checked first
			return rawset( G.core.tblobj( meta ), '__index', function( object, index )
				local val = statics[index]
				if not val then
					val = defaults[index]
					rawset( object, index, val )
				end
				return val
			end)
		end;]]--
		mods = setmetatable( {}, {
			-- prevent nasties
			__metatable = {},
			
			__newindex = function( g_mods, mod_name, mod_spec )
				local G, func_name, global_class = _G.G, 'G.core.mods[]', '_'
				
				if type(mod_name) ~= 'string' then
					io.write( func_name," - warning: invalid mod name, mod not registered.\n" )
					return
				end
				
				io.write( func_name," - registering mod ",mod_name,"... " )
				
				global_class = mod_name
				
				if g_mods[mod_name] then
					io.write( "failed (mod already registered)\n" )
					return
					
				elseif G[mod_name] then
					io.write( "failed (mod name in use by global property)\n" )
					return
					
				elseif type(mod_spec) ~= 'table' then
					io.write( "failed (mod_spec is not a table)\n" )
					return
					
				elseif type(mod_spec[global_class]) ~= 'table' then
					io.write( "failed (super class not found)\n" )
					return
					
				else
					io.write( "success\n" )
					
				end
				
				rawset( g_mods, mod_name, mod_spec)
				
				G[mod_name] = G.core.newObj(mod_name, global_class)
				
				io.write( func_name," - loaded mod ",mod_name," {" )
				
				io.write( "<global>" )
				for k in pairs(mod_spec) do
					if k ~= global_class then io.write( ",",k ) end
				end
				
				io.write( "}\n" )
			end
		} )
	}}
	
	--[[ begin internals ]]--
	
	-- class_name = Obj_toclassname( mod_name, class )
	local Obj_toclassname = function( mod_name, class ) return mod_name .. '/' .. class end
	
	-- mod_name, class = Obj_fromclassname( class_name )
	local class_pattern = '^([^/]*)/([^/]*)$'
	local Obj_fromclassname = function( class_name ) return string.match( class_name, class_pattern ) end
	
	-- obj, error_str = Obj_primitive_constructor( base_properties, class_name )
	-- obj, error_str = Obj_primitive_constructor( base_properties, mod_name, class )
	local Obj_primitive_constructor = function( base_properties, m, c )
		
		-- module name or class name, it must be a string.
		if type(m) ~= 'string' then return nil, "invalid parameters" end
		
		local mod_name, class
		
		if type(c) == 'string' then
			-- 3-parameter version
			mod_name, class = m, c
		else
			-- 2-parameter verison
			mod_name, class = Obj_fromclassname( m )
		end
		
		-- invalid parameters: return nil, message
		if type(mod_name) ~= 'string' or type(class) ~= 'string' then
			return nil, "invalid class name"
		end
		
		-- fetch module spec
		local o = G.core.mods[ mod_name ]; if type(o) ~= 'table' then return nil, "module not found" end
		
		-- fetch class metatable
		o = rawget(o, class); if type(o) ~= 'table' then return nil, "class not found" end
		
		-- return an object with the discovered metatable attached
		return setmetatable(rawset( G.core.tblobj( base_properties ), 'gstype', Obj_toclassname(mod_name, class) ),o), nil
	end
	
	--[[ end internals ]]--
	
	
	G.core.is_gstype = function(obj, compare)
		-- print('cmp',obj.gstype, compare, (((obj and obj.gstype) and obj.gstype == compare) and true) or false)
		return (((obj and obj.gstype) and obj.gstype == compare) and true) or false
	end
	G.core.is_ent = function(obj)
		-- print('cmp',obj.gstype, compare, (((obj and obj.gstype) and obj.gstype == compare) and true) or false)
		return (((obj and obj.gstype) and obj.gstype == 'Ent') and true) or false
	end
	
	local g_core_newent_opts_statics
	
	G.core.newEnt = function( base )
		local G=_G.G
		local ent = G.core.tblobj( base )
		
		local ent_Type = 'Ent'
		
		ent.gstype = ent_Type
		
		for mod_name, mod_spec in pairs(G.core.mods) do
			if mod_spec[ ent_Type ] then
				ent[mod_name] = G.core.newObjEx( {ent=ent}, mod_name, ent_Type )
			end
		end
		
		return setmetatable( ent, G.core.Obj_indexhelper (
			-- metatable , statics , defaults
			g_core_newent_opts_statics[1],
			g_core_newent_opts_statics[2],
			g_core_newent_opts_statics[3]
		) )
	end
	
	
	G.core.newObj = function( ... )
		
		-- try to create object
		local obj, msg = Obj_primitive_constructor( {}, ... )
		
		if type(msg) == 'string' then
			-- something went wrong
			error( "G.core.newObj() - " .. msg )
			return nil
		end
		
		return obj
	end
	
	G.core.newObjEx = function( base_properties, ... )
		
		-- try to create object
		local obj, msg = Obj_primitive_constructor( base_properties, ... )
		
		if msg then
			-- something went wrong
			error( "G.core.newObjEx() - " .. msg )
		end
		
		return obj
	end
	
	
	G.core.get_seq = gs._get_seq
	--[[function( child )
		local p = child._parent -- fewer lookups, just in case
		if p then
			local c, x, i = child, p[1], 1
			while x and x ~= c
			do i, x = i + 1, p[i]
			end
			return x and i or nil
		end
		return nil
	end]]--
	
	
	G.core.changeseq = function( container, old_pos, new_pos )
		local n = 'number'
		if type(old_pos) == n and type(new_pos) == n and new_pos ~= old_pos and old_pos >= 1 and new_pos >= 1 then
			local target_len = #container
			if old_pos <= target_len and new_pos <= target_len then
				table.insert( container, new_pos, table.remove(container, old_pos) )
			end
		end
		return container
	end
	
	
	G.core.moveto = function( target, new_parent )
		local G=_G.G
		if target._parent then
			local i = G.core.get_seq(target)
			if i then table.remove(target._parent, i) end
			target._parent = nil
		end
		if new_parent then
			local noloop, t = true, new_parent
			while noloop and t do noloop, t = (t ~= target), (t._parent) end
			if noloop then
				target._parent = new_parent
				table.insert(new_parent, target)
			end
		end
		return target
	end
	
	
	G.core.forSubObjects = gs._forSubObjects
	--[[function( root_obj, ifunc )
		if not root_obj or type(ifunc) ~= 'function' then return end
		
		local G=_G.G
		
		-- local ent, i = rawget(root_obj, 1), 1
		-- while ent do
			-- if ifunc(ent) then G.core.forSubObjects( ent, ifunc ) end
			-- i = i + 1; ent = rawget(root_obj, i)
		-- end
		local fso = G.core.forSubObjects
		for i=1,#root_obj do
			local ent = root_obj[i]
			if ent and ifunc(ent) then fso( ent, ifunc ) end
		end
	end]]--
	
	
	G.core.eradicate = function( s )
		-- first for all elements call eradicate
		for k, v in pairs(s) do
			if type(v.eradicate) == 'function' then v:eradicate() end
		end
		
		-- then remove from parent, if any.
		-- now it's safe for GC to clean this table and any child elements (also why this isn't the first step).
		G.core.moveto( s )
	end
	
	
	
	--[[ GEO ENTITY ]]--
	g_core_newent_opts_statics = {
		nil,{
			[ [[seq]] ]			= G.core.get_seq;
			[ [[changeseq]] ]	= G.core.changeseq;
			[ [[moveto]] ]		= G.core.moveto;
			[ [[forSubEnts]] ]	= G.core.forSubEnts;
			[ [[eradicate]] ]	= G.core.eradicate
		},nil
	}
	
	--[[ GEO GLOBALS ]]--
	
	-- G.forSubObjects( parent_obj, bool func( obj ) )
	G.forSubObjects = G.core.forSubObjects
	
	-- G.forEachEnt( bool func( Ent ) )
	-- legacy, to be removed.
	G.forEachEnt = function( ifunc )
		G.core.forSubObjects( G, ifunc )
	end
	
	G.newEnt = function( parent, base )
		return G.core.moveto( G.core.newEnt( base ), parent )
	end
	
	-- void G.LoadHooks()
	-- binds engine callbacks to GEO SDK
	G.LoadHooks = function()
		if G then
			if G.state then
				gs.main = function()
					return G and G.state and G.state() or false
				end
			end
			if G.render then
				gs.render_hook = function()
					return G and G.render and G.render:all() or false
				end
			end
		end
	end
	
end
print( "GEO SDK Loaded :D" )

do
	for i,m in ipairs({'state','render'}) do
		print( "=== base_module[ " .. m .. " ] ===" )
		dofile( 'GEO\\gsgeo_' .. m .. '.lua' )
		if not G[m] then error( "\nHrmmph: fix base_module[ " .. m .. " ] 1!!11!!1!!1!" ) end
	end
end
